Intro a NLP con R

codigo
analisis
tutorial
nlp
Author

Lic. Pedro Damian Orden

Published

December 21, 2022

Presentación

El análisis de contenidos es hoy una de las técnicas de investigación más utilizadas por los y las cientistas sociales, debido a las cantidades crecientes de texto generado, sobre todo de manera digital, en redes sociales, medios de comunicación, procesos burocráticos y muchas de las industrias culturales.

En programación una técnica subsidiaria al análisis de contenidos es el análisis de lenguaje natural1 o NLP (por sus siglas en ingles), puesto que por medio de una serie de algoritmos podemos estructurar y extraer información relevante de uno, varios o millones de textos, un cúmulo de acciones que si tuviéramos que sistematizarlas a mano, o con un procesador de textos, nos llevarían una eternidad.

La propuesta del documento es a partir de un caso aplicado, introducir un set indispensable de lógicas y herramientas de programación propias del NLP que permiten capturar, mensurar y graficar elementos recurrentes y/o significativos de los textos en tanto unidades de análisis.

Específicamente realizaremos una serie de técnicas EDA aplicadas al caso de los subtítulos de una película famosa, como una forma desfetichizar el aprendizaje técnico, acercando procesos de análisis complejos a la realidad cotidiana.

Comencemos!

NLP con R

Según Sarkar (2019) el NLP se define como un campo especializado de las ciencias de la computación, la ingeniería y la inteligencia artificial enraizado en la lingüística computacional. Este está principalmente enfocado en el diseño y construcción de aplicaciones y sistemas que permitan la interacción entre máquinas y lenguajes naturales utilizados por el ser humano.

En nuestra realidad profesional, podemos reconocer implementaciones de técnicas NLP en procesos tales como el monitoreo de redes sociales, la detección de noticias falsas, el filtrado de mensajes de odio, el análisis de campos abiertos encuestas o en la investigación de bienes culturales digitales.

Enfoque Tidy

En R existen múltiples aproximaciones para el desarrollo de modelo de análisis en clave NLP, en nuestro caso vamos a enfocarnos en el paradigma tidy (u ordenado).

El uso de principios de datos ordenados es una forma poderosa de hacer que el manejo de datos sea más fácil y efectivo. Tal como lo describe Hadley Wickham2, los datos ordenados se definen por contar con estructura tubular, organizada en filas y columnas.

En R este tipo de datos se almacenan en dataframes (o tibbles).

en este sentido, un dataframe será tidy si cada columna es una variable (tasa, indicador, etc) y cada fila es una unidad de análisis (persona, país, región etc…); es decir, cada celda contiene el valor de una variable para una unidad de análisis.

En nuestro caso de trabajo aplicaremos este enfoque, adaptado a la minería de texto, donde los datos se organizan en un token por fila.

Un token es una unidad significativa de texto, por ejemplo una palabra que nos interesa usar para el análisis, y la tokenización es el proceso de dividir el texto en tokens.

El token que se almacena en cada fila suele ser una sola palabra, pero también puede ser un n-grama, una oración o un párrafo.

Afortunadamente existe R con paquetes como tidytext, que utilizaremos a continuación y que brinda funciones para tokenizar rápidamente las unidades de texto de uso común para convertirlas a un formato tidy, de un término por fila.

Antes de empezar: unidades mínimas de texto en R

Repasemos/refresquemos rápidamente la dinámica de objetos y vectores con formato character, dentro de lo que son las lógicas de programación en R.

Por ejemplo:

Code
#un objeto con una letra
letra_a <- "a"

letra_a
[1] "a"
Code
class(letra_a) #generalmente son de tipo character
[1] "character"
Code
#un objeto con una palabra

palabra_auto <- "auto"

palabra_auto
[1] "auto"
Code
#un objeto con un vector de palabras

vector_palabras <- c("montaña", "mar", "nieve", "playa", "bosque", "laguna", "campo")

vector_palabras
[1] "montaña" "mar"     "nieve"   "playa"   "bosque"  "laguna"  "campo"  
Code
#un objeto con un vector con varias frases

vector_frases <- c("Trae tu propio sol",
    "La alegría es contagiosa. Pásalo...",
    "Lo mejor de la vida es gratis",
    "¿Quieres el bien? Haz el bien y vendrá",
    "Los que tienen prisa, tropiezan",
    "A veces se gana y otras se aprende",
    "Cuando menos lo esperas, todo sale bien",
    "Cree en ti y todo será posible",
    "Si no suma, que no reste",
    "No te busques, no te encuentres. Constrúyete")

#fuente https://www.cosmopolitan.com/es/consejos-planes/familia-amigos/g38492479/frases-cortas-bonitas/

vector_frases
 [1] "Trae tu propio sol"                          
 [2] "La alegría es contagiosa. Pásalo..."         
 [3] "Lo mejor de la vida es gratis"               
 [4] "¿Quieres el bien? Haz el bien y vendrá"      
 [5] "Los que tienen prisa, tropiezan"             
 [6] "A veces se gana y otras se aprende"          
 [7] "Cuando menos lo esperas, todo sale bien"     
 [8] "Cree en ti y todo será posible"              
 [9] "Si no suma, que no reste"                    
[10] "No te busques, no te encuentres. Constrúyete"
Code
#y así sucesivamente...

Como veremos a lo largo de este documento, los campos de texto pueden trabajarse con funciones como cualquier otro objeto de R. Veamos un ejemplo:

Code
library(tidyverse) #levantamos tidyverse

frase_random <- sample(vector_frases)%>% #randomizamos con una función el orden de las frases
  head(1) #nos quedamos con una al azar.

frase_random
[1] "A veces se gana y otras se aprende"
Code
#cada vez que demos play al bloque, las frases van a ir variando aleatoriamente.

Hecho! ya automatizamos nuestro primer bot autoayuda (?

Lo importante de este apartados es fijar que el texto se puede procesar como cualquier otro objeto en R.

Sigamos con lo que estábamos.

Datos y objetivos

Cuando trabajamos en NLP la unidad de análisis puede ser cualquier texto, digital o digitalizable, estructurado o no, con una extensión que pueda leer R o que deba ser transformada para ello.

Por ejemplo: comentarios en redes sociales, discursos, libros, notas periodísticas, canciones, documentos públicos, poesías.

Para nuestro caso de trabajo los datos a indagar serán los subtítulos de la mundialmente famosa película El Padrino 2 (1974). Nos interesará conocer cuales son las palabras más mencionadas y los personajes con más referencias en el film.

Carga

Los datos se ubican en la carpeta pelis y cuentan con una extensión .srt, un formato utilizado por las plataformas de video digitales para reproducir subtítulos, es un tipo de texto plano (del tipo .txt), pero presenta algunas complejidades a la hora de estructurarlo para el análisis.

Observemos inicialmente lo que ocurre cuando levantamos el archivo como si se tratara de un txt normal:

Code
#Extraemos los subitutlos de la peli como vienen, por ejemplo.
sub1 <- paste(readLines("pelis/padrino2.srt"))
head(sub1)
[1] "1"                                   
[2] "00:01:42,903 --> 00:01:48,075"       
[3] "El padrino nació como Vito Andolini,"
[4] "en el pueblo de Corleone en Sicilia."
[5] ""                                    
[6] "2"                                   

Pudimos cargar nuestro archivo pero de una forma sucia, sin formato, para nada tidy. Estructurar estos datos a mano llevaría algo de tiempo codeando…pero en R existe una librería para cada problema que se presenta.

SRT

La librería srt, de manera automática, formatea en un dataframe los subtítulos de la película que se nos ocurra, solo hace falta contar con el archivo .srt de entrada, del resto se encarga el sistema.

Veamos cómo funciona:

Code
library(srt)

padrino2 <- read_srt(path = "pelis/padrino2.srt", collapse = " ")

summary(padrino2)
       n              start              end            subtitle        
 Min.   :   1.0   Min.   :  102.9   Min.   :  108.1   Length:1423       
 1st Qu.: 356.5   1st Qu.: 2858.6   1st Qu.: 2861.2   Class :character  
 Median : 712.0   Median : 5756.7   Median : 5760.9   Mode  :character  
 Mean   : 712.0   Mean   : 6010.1   Mean   : 6013.7                     
 3rd Qu.:1067.5   3rd Qu.: 9037.7   3rd Qu.: 9041.9                     
 Max.   :1423.0   Max.   :11776.9   Max.   :11781.6                     

Mucho mejor! ordenamos datos de texto y obtuvimos un dataframe de 4 columnas: n, start, end y subtitle, con 1423 observaciones correspondientes a cada segmento de texto.

Tokenización

Puesto que nos interesa saber más sobre los términos y personajes, vamos a tokenizar la columna subtitle del dataframe padrino2.

Como ya lo hemos mencionado, la tokenización es una técnica que nos permite procesar conjuntos de datos de texto ordenados por medio de “tokens”.

Utilizaremos tidytext para llevar adelante la tokenización y el ordenamiento de nuestro datafame. Para ello corremos la librería y aplicamos la función unnest_tokens para descomponer la columna subtitle en palabras individuales.

Vamos a ver cómo es el proceso en código:

Code
library(tidytext)

padrino2_tokenizado<-padrino2 %>% 
  unnest_tokens(word, subtitle)# Aplicamos unnest_tokens, para crear la columna word. Generalmente le ponemos word al campo que supone palabras. 

#En programación las convenciones son importantes ya que nos ahorran trabajo.

print(padrino2_tokenizado) # vemos qué tiene
# A tibble: 10,163 x 4
       n start   end word    
   <int> <dbl> <dbl> <chr>   
 1     1  103.  108. el      
 2     1  103.  108. padrino 
 3     1  103.  108. nació   
 4     1  103.  108. como    
 5     1  103.  108. vito    
 6     1  103.  108. andolini
 7     1  103.  108. en      
 8     1  103.  108. el      
 9     1  103.  108. pueblo  
10     1  103.  108. de      
# ... with 10,153 more rows

Fin de la transformación. Ahora si tenemos nuestros datos en formato tidy. Podemos ver que logramos nuestro cometido, “desarmamos” subtitle para crear la columna word, que contiene todas las palabras de la película por separado.

Puesto que podemos trabajar el texto como cualquier otro dataset tubular, cabrá preguntarnos por ejemplo: cuantas veces se menciona la palabra padrino en “El Padrino 2”?. Para ello vamos filtrar la palabra padrino en la columna word del dataset padrino2_tokenizado.

Code
padrino2_tokenizado%>%
  filter(word=="padrino")
# A tibble: 5 x 4
      n start   end word   
  <int> <dbl> <dbl> <chr>  
1     1  103.  108. padrino
2   958 8460. 8466. padrino
3   959 8467. 8473. padrino
4  1107 9318. 9323. padrino
5  1108 9329. 9331. padrino
Code
#son 5!  

Iteremos el ejercicio haciendo un conteo simple de todas las palabras mencionadas en el film.

Code
library(kableExtra)

n_palabras<-padrino2_tokenizado %>% 
  count(word, sort=TRUE)

print(n_palabras)
# A tibble: 2,407 x 2
   word      n
   <chr> <int>
 1 que     347
 2 de      321
 3 a       281
 4 no      281
 5 la      167
 6 y       167
 7 el      152
 8 en      146
 9 lo      139
10 un      138
# ... with 2,397 more rows

Como podremos observar los primeros puestos de nuestra tabla están ocupados por artículos, conectores, etc. En sí no nos aportan demasiada información, a menos que estemos realizando algun tipo de análisis lingüístico específico. Por tanto, algo que suele hacerse para llevar adelante técnicas de NLP es remover todas aquellas palabras vacías que en inglés se denominan stopwords.

Code
#para eso utilizamos el paquete stopwords (ver hipervinculo arriba)
#install.packages("stopwords")
library(stopwords)

#Creamos los vectores

stop_es<- c(stopwords("spanish")) #armamos un vector con las stopwords en esp

#tambien vamos a quitar cirtas palabras que creamos que no aportan valor y no son stopwords (para ello aplicamos nuestro criterio).
palabras_inutiles <- c('i', 'aquí', 'sólo', 'si', 'sé', 'bien','quiero', 'cómo', 'usted')

datos_tidy<-n_palabras%>%
  filter(!(word %in% stop_es))%>% #filtra stopwords
  filter(!word%in%palabras_inutiles) %>% #filtra p inutiles
  filter(str_detect(word, "^[a-zA-z]|^#|^@"))%>%#OJO esto es nuevo tambien, remueve caracteres inutiles
  arrange(desc(n))#ordena

print(datos_tidy)
# A tibble: 2,182 x 2
   word         n
   <chr>    <int>
 1 michael     46
 2 corleone    43
 3 familia     36
 4 fredo       34
 5 roth        31
 6 padre       30
 7 vito        29
 8 puedo       24
 9 ahora       21
10 casa        21
# ... with 2,172 more rows

Primeros resultados

Dado que estamos llevando adelante un proceso exploratorio de datos, vamos a graficar las 20 palabras más mencionadas en “El Padrino 2” para dar cumplimiento al objetivo que nos planteamos al principio de nuestro documento.

Code
datos_tidy %>%
  .[1:20,] %>% #recuperamos las primeras 20
  ggplot()+
  # geom_col(aes(x=word),y=n)) #sin ordenar
  geom_col(aes(x=reorder(word,-n),y=n)) #ordenado.

Code
# datos_tidy %>%
#   .[1:5,] %>% #primeras 5
#   ggplot()+
#   geom_col(aes(x=reorder(word,-n),y=n)) 

Muy desprolijo no? La legibilidad de nuestros datos es clave para la comunicación, mejoremos el gráfico tocando agunos de sus parámetros fundamentales.

Code
datos_tidy %>%
  .[1:20,] %>%
  ggplot()+
  aes(x=reorder(word,-n),y=n)+
  geom_col(fill="#9A0000", alpha=0.5)+
  theme(axis.text.x = element_text(angle = 55)) +
  labs(title="Zoom in: Palabras más usadas en el El Padrino 2.", 
       subtitle = "Frecuencia de términos utilizados en la película.",
       x ="Palabras principales", 
       y = "Frecuencia", 
       caption="Elaboración propia, en base a los subtítulos de la película.")

Ahora sí! ya entramos en tema. Qué te sugieren estos primeros resultados?

En resumen, qué vimos hoy?

Conocimos los fundamentos generales del análisis de lenguaje natural para trabajar con textos y el enfoque tidy para estructurar y manipular datos. Dos enfoques complementarios para nuestra intervención profesional.

Aprendimos cómo cargar subtítulos de películas en R de manera ordenada con el paquete srt.

Mientras codeamos incorporamos las primeras nociones de técnicas nlp, llevando adelante procesos de limpieza de datos, tokenización y visualización de frecuencias para implementar el abordaje exploratorio de un texto.

Bonus

El 21 de diciembre de 2022 utilizamos este tutorial como material del taller abierto del Núcleo de Innovación Social y puede consultarse de manera libre aquí:

Footnotes

  1. La analítica de textos es una subárea de los métodos NLP.↩︎

  2. Ver también: https://tidyverse.tidyverse.org/articles/manifesto.html↩︎

Reuse

Citation

BibTeX citation:
@online{pedro damian orden2022,
  author = {Pedro Damian Orden, Lic.},
  title = {Intro a {NLP} Con {R}},
  date = {2022-12-21},
  url = {https://tecysoc.netlify.app/posts/intro a nlp},
  langid = {en}
}
For attribution, please cite this work as:
Pedro Damian Orden, Lic. 2022. “Intro a NLP Con R.” December 21, 2022. https://tecysoc.netlify.app/posts/intro a nlp.